#include <memory.h>
#include "buf.h"
#include "db.h"

//----------------------------------------
// Constructor of the class BufMgr
//----------------------------------------

BufMgr::BufMgr(const unsigned int bufs)
:bufMap(bufs)
{
	numBufs = bufs;
	clockHand = 0;

	bufPool = new Page[bufs];
	bufTable = new BufDesc[bufs];	
	/*
	for(int i = 0; i < bufs; i++)
	{
		//initialize buf pool
		bufPool[i] = Page();
		//initialize buf table
		bufTable[i] = BufDesc();
	}
	*/
	//buf map is already empty


}


BufMgr::~BufMgr() 
{
	// Your solution goes here
	//flush all dirty pages
	for(unsigned int i = 0; i < numBufs; i++)
	{
		if(bufTable[i].dirty)
		{
			bufTable[i].file->writePage(bufTable[i].pageNo, &bufPool[i]);
			bufStats.diskwrites++;
		}
	}
	delete [] bufPool;
	delete [] bufTable;
}


const Status BufMgr::allocBuf(unsigned int & frame) 
{ 
	// Your solution goes here
	//loop is only used to make sure we don't loop clock more than 2x
	for(unsigned int i = 0; i < 2 * numBufs; i++)
	{
		
		advanceClock();
		if(bufTable[clockHand].valid == false)
		{
			//prepare frame for use
			//bufTable-
			frame = clockHand;
			//bufMap.remove(bufTable[clockHand].file, bufTable[clockHand].pageNo);
			bufTable[clockHand].Clear();
			return OK;

		}
		if(bufTable[clockHand].loved == true)
		{
			bufTable[clockHand].loved = false;
			continue;
		}
		if(bufTable[clockHand].pinCnt > 0)
		{
			continue;
		}
		if(bufTable[clockHand].dirty == true)
		{
			Status write_back = 
				bufTable[clockHand].file->writePage(bufTable[clockHand].pageNo, &bufPool[clockHand]);

			if(write_back != OK)
			{
				return write_back;
			}

			bufStats.diskwrites++;
		}
		//everything is good, just do normal frame preparation
		//need to remove this from the buf map since we are replacing it
		Status remove_status = bufMap.remove(bufTable[clockHand].file, bufTable[clockHand].pageNo);
		if(remove_status != OK)
			return remove_status;

		//clear this frame
		bufTable[clockHand].Clear();
		frame = clockHand;
		if(frame < 0 || frame >= numBufs)
			return BADFRAMEREAD;

		return OK;
	}

	return BUFFEREXCEEDED;
	
}

	
const Status BufMgr::readPage(File* file, const int PageNo, Page*& page)
{
	// Your solution goes here
	//first check to see if the page is in the buffer pool
	unsigned int frameNo;
	Status lookup = bufMap.lookup(file, PageNo, frameNo);
	if(lookup != OK && lookup != BUFMAPNOTFOUND)
		return lookup;

	bufStats.accesses++;

	//if already in the pool
	if(lookup == OK)
	{
		if(frameNo < 0 || frameNo >= numBufs || bufTable[frameNo].valid == false)
		{
			return BADBUFFER;
		}
		bufTable[frameNo].pinCnt++;
		page = &bufPool[frameNo];
		return OK;
	}
	//if it's not in the pool allocate it
	else
	{
		Status alloc_buf = allocBuf(frameNo);
		if(alloc_buf != OK)
			return alloc_buf;
		

		if(frameNo < 0 || frameNo >= numBufs)
		{
			return BADBUFFER;
		}

		Status page_read = file->readPage(PageNo, &bufPool[frameNo]);
		if(page_read != OK)
			return page_read;
		bufStats.diskreads++;

		//set the page and clear the buf table for that frame
		Status insert_status = bufMap.insert(file, PageNo, frameNo);
		if(insert_status != OK)
			return insert_status;

	
		page = &bufPool[frameNo];
		//bufPool[frameNo] = *lPage;
		bufTable[frameNo].Set(file, PageNo);
		return OK;
	}

}


const Status BufMgr::unPinPage(File* file, const int PageNo, const bool dirty, const bool love) 
{
	//First get the frame No
	unsigned int frameNo;
	Status lookup = bufMap.lookup(file, PageNo, frameNo);
	if(lookup != OK)
		return lookup;

	if(frameNo < 0 || frameNo >= numBufs || bufTable[frameNo].valid == false)
	{
		return BADBUFFER;
	}

	if(bufTable[frameNo].pinCnt>0)
	{
		bufTable[frameNo].pinCnt--;
	}
	else
	{
		return PAGENOTPINNED;
	}
	if(love==true)
	{
		bufTable[frameNo].loved = true;
	}
	if(dirty==true)
	{
		bufTable[frameNo].dirty = true;
	}
	return OK;
}


const Status BufMgr::flushFile(File* file) 
{
	//first check to confirm no pages are pinned
	for(unsigned int i = 0; i < numBufs; i++)
	{
		if(bufTable[i].file == file && bufTable[i].pinCnt > 0)
		{
			//If any are, we should return page pinned and not flush the file
			return PAGEPINNED;
		}
	}
	//scan buf table
	for(unsigned int i = 0; i < numBufs; i++)
	{
		//if the buf table is this file and it's dirty
		if(bufTable[i].file == file && bufTable[i].dirty)
		{
			//write the buffer pool for this i to it's file
			Status write = file->writePage(bufTable[i].pageNo, &bufPool[i]);
			if(write != OK)
				return write;

			bufStats.diskwrites++;
		}
	}
	return OK;
}


const Status BufMgr::disposePage(File* file, const int pageNo)
{
	if(pageNo == 0)
	{
		return BADPAGENO;
	}
	Status dispose_status = file->disposePage(pageNo);
	if(dispose_status != OK)
	{
		return dispose_status;
	}
	bufStats.diskwrites++;

	unsigned int frameNo;
	Status lookup = bufMap.lookup(file, pageNo, frameNo);

	//if we don't have this frame in the buf map we're done here and OK
	

	if(lookup == BUFMAPNOTFOUND)
	{
		return OK;
	}
	//any other errors should be reported, else we are good
	if(lookup != OK)
	{
		return lookup;
	}
	//check to make sure frame is within the frame range and valid
	if(frameNo < 0 || frameNo >= numBufs || bufTable[frameNo].valid != true)
	{
		return BADBUFFER;
	}

	//check to see if this page is pinned before removing it
	if(bufTable[frameNo].pinCnt > 0)
	{
		return PAGEPINNED;
	}

	//now remove the page from our buffer frame, if we have gotten this far we know it
	//is in the buffer frame

	//remove the page from the buff map
	Status buf_remove = bufMap.remove(file, pageNo);
	if(buf_remove != OK)
		return buf_remove;

	//reset all the flags for this frame
	bufTable[frameNo].Clear();
		
	return OK;
}


const Status BufMgr::allocPage(File* file, int& pageNo, Page*& page) 
{
	bufStats.accesses++;
	//first allocate a buf to store the page
	unsigned int frameNo;
	Status alloc_buf = allocBuf(frameNo);
	if(alloc_buf != OK)
	{
		return alloc_buf;
	}
	if(frameNo < 0 || frameNo >= numBufs)
	{
		return BADFRAMEREAD;
	}

	// next allocate the page (this sets pageNo)
	Status alloc_status = file->allocatePage(pageNo);
	if(alloc_status != OK)
		return alloc_status;
	
	bufStats.diskwrites++;
	//put the page into memory from the frame we got from alloc buf
	
	//put it into the map
	Status map_insert = bufMap.insert(file, pageNo, frameNo);
	if(map_insert != OK)
	{
		return map_insert;
	}
	//put it into the desc table
	bufTable[frameNo].Set(file, pageNo);
	page = &bufPool[frameNo];

	return OK;
}



// return the number of pages in the buffer pool that are not pinned
int BufMgr::numUnpinnedPages()
{
  int numFree = 0;
  BufDesc* tmpbuf;

  for (unsigned i=0; i<numBufs; i++) 
  {
    tmpbuf = &(bufTable[i]);
    if (tmpbuf->pinCnt == 0) numFree++;
  }

  return numFree;
}

void BufMgr::printSelf(void) {

  BufDesc* tmpbuf;
  
  cout << endl << "Print buffer...\n";
  for (unsigned i=0; i<numBufs; i++) {
    tmpbuf = &(bufTable[i]);
    cout << i << "\t" << (char*)(&bufPool[i]) 
      << "\tpinCnt: " << tmpbuf->pinCnt;
    
    if (tmpbuf->valid == true)
      cout << "\tvalid\n";
    cout << endl;
  };
}
